/*
 * ESObject.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import java.util.Enumeration;

import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.exceptions.ProgrammingError;
import org.scriptme.ecmascript.interpreter.Evaluator;
import org.scriptme.ecmascript.interpreter.EcmaScriptHashtable;
import org.scriptme.ecmascript.interpreter.ScopeChain;

/**
 * The Class ESObject.
 */
public abstract class ESObject extends ESValue {

	/** Contains the properties of this object. */
	protected EcmaScriptHashtable properties;

	/** The evaluator owning this object. */
	protected Evaluator evaluator;

	/**
	 * The prototype of this object ([[prototype]] in the standard, not the
	 * "prototype" property of functions!).
	 */
	private ESObject prototype = null;

	// Prepare common names and their hash value
	/** The Constant TOSTRINGstring. */
	static private final String TOSTRINGstring = ("toString").intern();

	/** The Constant TOSTRINGhash. */
	static private final int TOSTRINGhash = TOSTRINGstring.hashCode();

	/** The Constant VALUEOFstring. */
	static private final String VALUEOFstring = ("valueOf").intern();

	/** The Constant VALUEOFhash. */
	static private final int VALUEOFhash = VALUEOFstring.hashCode();

	/**
	 * Create an object with a specific prototype (which may be null) in the
	 * context of a specific evaluator (which may not be null) Uses the default
	 * hashTable size.
	 * 
	 * @param prototype
	 *            the prototype
	 * @param evaluator
	 *            the evaluator
	 */
	protected ESObject(ESObject prototype, Evaluator evaluator) {
		this.prototype = prototype;
		this.properties = new EcmaScriptHashtable();
		this.evaluator = evaluator; // It will crash somewhere if null...
	}

	/**
	 * Create an object with a specific prototype (which may be null) in the
	 * context of a specific evaluator (which may not be null) Uses the
	 * specified hashTable size, which should be a prime. size is usefull for
	 * very small (arguments) or very large objects.
	 * 
	 * @param prototype
	 *            the prototype
	 * @param evaluator
	 *            the evaluator
	 * @param initialSize
	 *            the initial size
	 */
	protected ESObject(ESObject prototype, Evaluator evaluator, int initialSize) {
		this.prototype = prototype;
		this.properties = new EcmaScriptHashtable(initialSize);
		this.evaluator = evaluator; // It will crash somewhere if null...
	}

	/**
	 * Get the evaluator for this object.
	 * 
	 * @return the evaluator
	 */
	public final Evaluator getEvaluator() {
		return evaluator;
	}

	/**
	 * All objects and thir subclasses are non primitive.
	 * 
	 * @return false
	 */
	public final boolean isPrimitive() {
		return false;
	}

	/**
	 * Implements the [[prototype]] property (see 8.6.2)
	 * 
	 * @return The prototype object or null
	 */
	public ESObject getPrototype() {
		return prototype;
	}

	/**
	 * Return the name of the class of objects ([[class]]), as used in the
	 * default toString method of objects (15.2.4.2)
	 * 
	 * @return the [[Class]] property of this object
	 */
	public String getESClassName() {
		return "Object";
	}

	/**
	 * Return a code indicating the type of the object for the implementation of
	 * the "==" operator.
	 * 
	 * @return A type code
	 */
	public int getTypeOf() {
		return EStypeObject;
	}

	/**
	 * Either return the property value of the specified property in the current
	 * object, or lookup the next object in the scope chain if there is one. If
	 * there is nones, generate an error message.
	 * <P>
	 * This routine must be overriden by subclass which change the
	 * implementation of getProperty.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param previousScope
	 *            the previous scope
	 * @param hash
	 *            the hash
	 * 
	 * @return The value of the specified variable
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                if not found in any scope
	 */
	public ESValue getPropertyInScope(String propertyName,
			ScopeChain previousScope, int hash) throws EcmaScriptException {
		// We directly attempt to get a value, without trying
		// if there is such a property first (for performance reasons)
		// If there is no property, 'undefined' is returned
		ESValue value = getProperty(propertyName, hash);

		if (value == ESUndefined.theUndefined) {
			// Here there are two cases - there was no property
			// with that name (and we must
			// lookup in the previous scope), or there was one
			// but with a value of 'undefined', and we must return
			// undefined. Currently the only way to find out if to
			// check directly (that makes a second table lookup).
			boolean hasTheProperty = properties.containsKey(propertyName, hash);

			if (!hasTheProperty) {
				if (previousScope == null) {
					throw new EcmaScriptException("global variable '"
							+ propertyName + "' does not have a value");
				} else {
					value = previousScope.getValue(propertyName, hash);
				}
			}
		}
		return value;
	}

	/**
	 * Get the property by name (see 8.6.2.1) propagating to the prototype if
	 * required
	 * 
	 * @param propertyName
	 *            the property name
	 * @param hash
	 *            the hash
	 * 
	 * @return The property or <em>undefined</em>
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Error in host objects ?
	 */
	public ESValue getProperty(String propertyName, int hash)
			throws EcmaScriptException {
		ESValue value = (ESValue) properties.get(propertyName, hash);
		if (value == null) {
			if (prototype == null) {
				value = ESUndefined.theUndefined;
			} else {
				value = prototype.getProperty(propertyName, hash);
			}
		}
		return value;
	}

	/**
	 * Get the property by index value. By default the index is converted to a
	 * string, but this can be optimized for arrays.
	 * <P>
	 * This is not the same as the indexed properties of the first version of
	 * JavaScript and does not allow to access named properties other than the
	 * property using the integer string representation as a name.
	 * 
	 * @param index
	 *            the index
	 * 
	 * @return The property or <em>undefined</em>
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Error in host objects ?
	 */
	public ESValue getProperty(int index) throws EcmaScriptException {
		String iString = Integer.toString(index);
		return getProperty(iString, iString.hashCode());
	}

	/**
	 * Checks for property.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param hash
	 *            the hash
	 * 
	 * @return true, if successful
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	public boolean hasProperty(String propertyName, int hash)
			throws EcmaScriptException {
		boolean found = properties.containsKey(propertyName, hash);
		if (!found && prototype != null) {
			found = prototype.hasProperty(propertyName, hash);
		}
		return found;
	}

	/**
	 * Checks if is hidden property.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param hash
	 *            the hash
	 * 
	 * @return true, if is hidden property
	 */
	public boolean isHiddenProperty(String propertyName, int hash) {
		return properties.isHidden(propertyName, hash);
	}

	/**
	 * Indicates that the getProperties return an enumerator to the index rather
	 * rather than to the value index (see ESWrapper).
	 * 
	 * @return false
	 */
	public boolean isDirectEnumerator() {
		return false;
	}

	/**
	 * Returns an enumerator for the key elements of this object, that is all is
	 * enumerable properties and the (non hidden) ones of its prototype, etc...
	 * As used for the for in statement.
	 * 
	 * @return the enumerator
	 */
	public Enumeration getProperties() {
		return new Enumeration() {
			Enumeration props = properties.keys();
			String currentKey = null;
			int currentHash = 0;
			boolean inside = false;

			public boolean hasMoreElements() {
				if (currentKey != null)
					return true;
				while (props.hasMoreElements()) {
					currentKey = (String) props.nextElement();
					currentHash = currentKey.hashCode();
					if (inside) {
						if (properties.containsKey(currentKey, currentHash))
							continue;
					} else {
						if (isHiddenProperty(currentKey, currentHash))
							continue;
					}
					return true;
				}
				if (!inside && prototype != null) {
					inside = true;
					props = prototype.getProperties();
					while (props.hasMoreElements()) {
						currentKey = (String) props.nextElement();
						currentHash = currentKey.hashCode();
						if (properties.containsKey(currentKey, currentHash))
							continue;

						return true;
					}
				}
				return false;
			}

			public Object nextElement() {
				if (hasMoreElements()) {
					String key = currentKey;
					currentKey = null;
					return key;
				} else {
					throw new java.util.NoSuchElementException();
				}
			}
		};
	}

	/**
	 * Get all properties (including hidden ones), for the command.
	 * 
	 * @return An enumeration of all properties (visible and hidden).
	 * 
	 * @listall of the interpreter. Include the visible properties of the
	 *          prototype (that is the one added by the user) but not the hidden
	 *          ones of the prototype (otherwise this would list all functions
	 *          for any object).
	 */
	public Enumeration getAllProperties() {
		return new Enumeration() {
			String[] specialProperties = getSpecialPropertyNames();
			int specialEnumerator = 0;
			Enumeration props = properties.keys(); // all of object properties
			String currentKey = null;
			int currentHash = 0;
			boolean inside = false; // true when examing prototypes properties

			public boolean hasMoreElements() {
				// OK if we already checked for a property and one exists
				if (currentKey != null)
					return true;
				// Loop on special properties first
				if (specialEnumerator < specialProperties.length) {
					currentKey = specialProperties[specialEnumerator];
					currentHash = currentKey.hashCode();
					specialEnumerator++;
					return true;
				}
				// loop on standard or prototype properties
				while (props.hasMoreElements()) {
					currentKey = (String) props.nextElement();
					currentHash = currentKey.hashCode();
					if (inside) {
						if (properties.containsKey(currentKey, currentHash))
							continue;
						// SHOULD CHECK IF NOT IN SPECIAL
					}
					return true;
				}
				// If prototype properties have not yet been examined, look for
				// them
				if (!inside && prototype != null) {
					inside = true;
					props = prototype.getProperties();
					while (props.hasMoreElements()) {
						currentKey = (String) props.nextElement();
						currentHash = currentKey.hashCode();
						if (properties.containsKey(currentKey, currentHash))
							continue;

						return true;
					}
				}
				return false;
			}

			public Object nextElement() {
				if (hasMoreElements()) {
					String key = currentKey;
					currentKey = null;
					return key;
				} else {
					throw new java.util.NoSuchElementException();
				}
			}
		};
	}

	/**
	 * Put the property by name (see 8.6.2.2), ignoring if read only (integrate
	 * functionality of canPut) and creating it if needed and possible.
	 * <P>
	 * The routine implement the functionality of the canPut attribute.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param propertyValue
	 *            the property value
	 * @param hash
	 *            the hash
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Error in host objects ?
	 */
	public void putProperty(String propertyName, ESValue propertyValue, int hash)
			throws EcmaScriptException {
		properties.put(propertyName, hash, false, false, propertyValue);
	}

	/**
	 * Put the property by index value. By default the index is converted to a
	 * string, but this can be optimized for arrays.
	 * <P>
	 * This is not the same as the indexed properties of the first version of
	 * JavaScript and does not allow to access named properties other than the
	 * property using the integer string representation as a name.
	 * 
	 * @param index
	 *            the index
	 * @param propertyValue
	 *            the property value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Error in host objects ?
	 */
	public void putProperty(int index, ESValue propertyValue)
			throws EcmaScriptException {
		String iString = Integer.toString(index);
		putProperty(iString, propertyValue, iString.hashCode());
	}

	/**
	 * Put the property as hidden. This is mostly used by initialization code,
	 * so a hash value is computed localy and the string is interned.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param propertyValue
	 *            the property value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not used
	 */
	public void putHiddenProperty(String propertyName, ESValue propertyValue)
			throws EcmaScriptException {
		propertyName = propertyName.intern();
		int hash = propertyName.hashCode();
		properties.put(propertyName, hash, true, false, propertyValue);
	}

	/**
	 * Implements the [[delete]] function (8.6.2.5), only called by the DELETE
	 * operator. Should return true if the propery does not exist any more (or
	 * did not exist at all) after the return.
	 * <P>
	 * This routine must implement the DontDelete attribue too.
	 * 
	 * @param propertyName
	 *            the property name
	 * @param hash
	 *            the hash
	 * 
	 * @return true if the property is not present anymore
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Not used
	 */
	public boolean deleteProperty(String propertyName, int hash)
			throws EcmaScriptException {
		properties.remove(propertyName, hash);
		return true; // either it did not exist or was deleted !
	}

	/**
	 * Implements [[DefaultValue]] with hint.
	 * 
	 * @param hint
	 *            A type hint (only string or number)
	 * 
	 * @return the default value of this object
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Propagated or bad hint
	 */
	public ESValue getDefaultValue(int hint) throws EcmaScriptException {
		ESValue theResult = null;
		ESValue theFunction = null;

		if (hint == ESValue.EStypeString) {
			theFunction = this.getProperty(TOSTRINGstring, TOSTRINGhash);
			if (theFunction instanceof ESObject) {
				theResult = theFunction.callFunction(this, new ESValue[0]);
				if (theResult.isPrimitive()) {
					return theResult;
				}
			}
			theFunction = this.getProperty(VALUEOFstring, VALUEOFhash);
			if (theFunction instanceof ESObject) {
				theResult = theFunction.callFunction(this, new ESValue[0]);
				if (theResult.isPrimitive()) {
					return theResult;
				}
			}
			// Throw errror on super to avoid evaluating this with as a string,
			// as this is exactly what we cannot do.
			throw new EcmaScriptException("No default value for "
					+ super.toString() + " and hint " + hint);
		} else if (hint == ESValue.EStypeNumber) {
			theFunction = this.getProperty(VALUEOFstring, VALUEOFhash);
			if (theFunction instanceof ESObject) {
				theResult = theFunction.callFunction(this, new ESValue[0]);
				if (theResult.isPrimitive()) {
					return theResult;
				}
			}
			theFunction = this.getProperty(TOSTRINGstring, TOSTRINGhash);
			if (theFunction instanceof ESObject) {
				theResult = theFunction.callFunction(this, new ESValue[0]);
				if (theResult.isPrimitive()) {
					return theResult;
				}
			}
		}
		throw new EcmaScriptException("No default value for " + this
				+ " and hint " + hint);
	}

	/**
	 * Implements [[DefaultValue]] with no hint
	 * <P>
	 * The default is different for dates.
	 * 
	 * @return the default value of this object
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                Propagated
	 */
	public ESValue getDefaultValue() throws EcmaScriptException {

		return this.getDefaultValue(EStypeNumber);
	}

	/**
	 * Call a function object - not implemented for default objecr.
	 * 
	 * @param thisObject
	 *            the this object
	 * @param arguments
	 *            the arguments
	 * 
	 * @return The calculated value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                thrown because the function is not implemented
	 */
	public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		throw new EcmaScriptException("No function defined on: " + this);
	}

	/**
	 * A construct as thisObject.functionName() was detected, The functionName
	 * is looked up, then a call is made. This avoid creating a dummy function
	 * object when one does not exists, like for the ESWrapper objects (where
	 * functions are really java methods).
	 * <P>
	 * Only method which do not use the standard EcmaScript function evaluation
	 * mechanism need to override this method.
	 * 
	 * @param target
	 *            The original target (for recursive calls)
	 * @param evaluator
	 *            the evaluator
	 * @param functionName
	 *            the function name
	 * @param arguments
	 *            the arguments
	 * 
	 * @return The result of calling the function
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * @throws Exception
	 *             the exception
	 * 
	 * @exception EcmaScriptException
	 *                Function not defined
	 * @exception NoSuchMethodException
	 *                Method not found
	 */
	public ESValue doIndirectCall(Evaluator evaluator, ESObject target,
			String functionName, ESValue[] arguments)
			throws EcmaScriptException, Exception {
		ESValue theFunction = (ESValue) properties.get(functionName,
				functionName.hashCode());

		if (theFunction == null) {
			if (prototype == null) {
				throw new EcmaScriptException("The function '" + functionName
						+ "' is not defined for object '" + target.toString()
						+ "'");
			} else {
				return prototype.doIndirectCall(evaluator, target,
						functionName, arguments);
			}
		} else {
			return theFunction.callFunction(target, arguments);
		}
	}

	// A routine which may return a function as the value of a builtin
	// property must override this function
	/**
	 * Do indirect call in scope.
	 * 
	 * @param evaluator
	 *            the evaluator
	 * @param previousScope
	 *            the previous scope
	 * @param thisObject
	 *            the this object
	 * @param functionName
	 *            the function name
	 * @param hash
	 *            the hash
	 * @param arguments
	 *            the arguments
	 * 
	 * @return the eS value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	public ESValue doIndirectCallInScope(Evaluator evaluator,
			ScopeChain previousScope, ESObject thisObject, String functionName,
			int hash, ESValue[] arguments) throws EcmaScriptException {
		ESValue theFunction = (ESValue) properties.get(functionName, hash);
		if (theFunction == null) {
			if (previousScope == null) {
				throw new EcmaScriptException("no global function named '"
						+ functionName + "'");
			} else {
				return previousScope.doIndirectCall(evaluator, thisObject,
						functionName, hash, arguments);
			}
		}
		return theFunction.callFunction(thisObject, arguments);
	}

	/**
	 * Call the constructor - not implemented on default object.
	 * 
	 * @param thisObject
	 *            the this object
	 * @param arguments
	 *            the arguments
	 * 
	 * @return The created obbjecr
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                thrown because this function is not implemented
	 */
	public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		throw new EcmaScriptException("No constructor defined on: " + this);
	}

	/**
	 * Return a double value for this object if possible.
	 * 
	 * @return The double value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If not a suitable primitive
	 */
	public double doubleValue() throws EcmaScriptException {
		ESValue value = ESUndefined.theUndefined;
		double d = Double.NaN;
		try {
			value = toESPrimitive(EStypeNumber);
			d = value.doubleValue();
		} catch (EcmaScriptException e) {
			throw new ProgrammingError(e.getMessage());
		}
		return d;
	}

	/**
	 * Return the boolean value of this object if possible.
	 * 
	 * @return the boolean value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If not a suitable primitive
	 */
	public boolean booleanValue() throws EcmaScriptException {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		ESValue value = ESUndefined.theUndefined;
		String string = null;
		try {
			value = toESPrimitive(EStypeString);
		} catch (EcmaScriptException e) {
			return this.toDetailString();
		}
		string = value.toString();
		return string;
	}

	/**
	 * Convert to an object.
	 * 
	 * @param evaluator
	 *            the evaluator
	 * 
	 * @return This
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                not thrown
	 */
	public final ESValue toESObject(Evaluator evaluator)
			throws EcmaScriptException {
		return this;
	}

	/**
	 * Convert to a primitive.
	 * 
	 * @param preferedType
	 *            the prefered type
	 * 
	 * @return The primitive value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If no suitable default value
	 */
	public final ESValue toESPrimitive(int preferedType)
			throws EcmaScriptException {
		return getDefaultValue(preferedType);
	}

	/**
	 * Convert to a primitive.
	 * 
	 * @return The primitive value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EcmaScriptException
	 *                If no suitable default value
	 */
	public final ESValue toESPrimitive() throws EcmaScriptException {
		return getDefaultValue();
	}

	/**
	 * Return a Java object which is the object to pass to Java routines called
	 * by FESI. By default wrap the ESObject in a wrapper object, used by the
	 * jslib. Overriden by subclass if a better type can be found.
	 * 
	 * @return a wrapper object over this ESObject.
	 */
	public Object toJavaObject() {
		return new JSWrapper(this, evaluator);
	}

	/**
	 * Return the name of the type of the object for the typeof operator.
	 * 
	 * @return The name of the type as a String
	 */
	public String getTypeofString() {
		return "object";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESValue#toDetailString()
	 */
	public String toDetailString() {
		return "ES:[" + getESClassName() + "]";
	}

	/**
	 * Return true to indicate that this value is composite.
	 * 
	 * @return true
	 */
	public boolean isComposite() {
		return true;
	}

	/**
	 * Return the list of proprties which are not listed by getAll, that is all
	 * special properties handled directly by getProperty, which are not in the
	 * property hash table (they are considered hidden) Must be overriden by a
	 * subclass which overrides getProperty!
	 * 
	 * return The array of special property names.
	 * 
	 * @return the special property names
	 */
	public String[] getSpecialPropertyNames() {
		return new String[0];
	}

	/**
	 * Get an enumeration of the description of various aspects of the object,
	 * including all properties.
	 * 
	 * @return the all descriptions
	 */
	public Enumeration getAllDescriptions() {
		return new Enumeration() {
			String[] specialProperties = getSpecialPropertyNames();
			int specialEnumerator = 0;
			Enumeration props = properties.keys();
			String currentKey = null;
			int currentHash = 0;
			boolean inside = false;
			boolean inSpecial = true;

			public boolean hasMoreElements() {
				// If we have one already, send it
				if (currentKey != null)
					return true;
				// Loop on special properties first
				if (specialEnumerator < specialProperties.length) {
					currentKey = specialProperties[specialEnumerator];
					currentHash = currentKey.hashCode();
					specialEnumerator++;
					return true;
				}
				inSpecial = false;
				// Otherwise check in current enumeration
				while (props.hasMoreElements()) {
					currentKey = (String) props.nextElement();
					currentHash = currentKey.hashCode();
					// if (inside) {
					// if (properties.containsKey(currentKey, currentHash))
					// continue;
					// }
					return true;
				}
				// Got to prototype enumeration if needed
				if (!inside && prototype != null) {
					inside = true;
					props = prototype.getProperties();
					while (props.hasMoreElements()) {
						currentKey = (String) props.nextElement();
						currentHash = currentKey.hashCode();
						// if (properties.containsKey(currentKey, currentHash))
						// continue;
						return true;
					}
				}
				return false;
			}

			public Object nextElement() {
				if (hasMoreElements()) {
					String key = currentKey;
					int hash = key.hashCode();
					currentKey = null;
					ESValue value = null;
					try {
						value = ESObject.this.getProperty(key, hash);
					} catch (EcmaScriptException e) {
						throw new ProgrammingError("Unexpected exception " + e);
					}
					String propertyKind;
					if (inSpecial) {
						propertyKind = "HIDDEN";
					} else if (inside && properties.containsKey(key, hash)) {
						propertyKind = "INVISIBLE";
					} else {
						propertyKind = isHiddenProperty(key, hash) ? "HIDDEN"
								: "VISIBLE";
					}
					propertyKind += (inside ? " PROTOTYPE" : " OBJECT");
					propertyKind += " PROPERTY";
					return new ValueDescription(key, propertyKind, value
							.toString());
				} else {
					throw new java.util.NoSuchElementException();
				}
			}
		};
	}

	/**
	 * Returns a full description of the value, with the specified name.
	 * 
	 * @param name
	 *            The name of the value to describe
	 * 
	 * @return the description of this value
	 */
	public ValueDescription getDescription(String name) {
		return new ValueDescription(name, "OBJECT", this.toString());
	}
}
